using Microsoft.AspNetCore.Mvc;
using Daemon.Service.Contract;
using Daemon.Model;
using Microsoft.EntityFrameworkCore;
using Daemon.Common;
using Daemon.Data.Infrastructure.FrameWork;
using Daemon.Data.Substructure.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Daemon.Infrustructure.EF.Executor;
using Daemon.Model.Entities;
using System.Net;
namespace DaemonAPI.Controllers
{
    public abstract class BaseSearchV2Controller<TDomainEntity, TContext, TIdentityType> : ControllerBase
        where TDomainEntity : class, IGridEntity<TIdentityType>, new()
        where TContext : ApiDBContent, new()
    {
        private string _identityName;

        protected IBaseGridService<TDomainEntity, TContext, TIdentityType> GridService { get; }

        public BaseSearchV2Controller(IBaseGridService<TDomainEntity, TContext, TIdentityType> baseGridService)
        {
            GridService = baseGridService;
        }

        protected virtual string IdentityName
        {
            get
            {
                if (string.IsNullOrEmpty(_identityName))
                {
                    _identityName = GridExecutor.GetIdentityName<TDomainEntity, TIdentityType>();
                }

                return _identityName;
            }
        }

        /// <summary>
		/// Get an aggregate result for the requested field
		/// </summary>
		/// <param name="aggregateItem">The object of requested field and aggregate operation</param>
		/// <param name="searchCondition">An object contains different search conditions</param>
		/// <returns>Aggregate searches return an integer</returns>
		[Route("aggregate")]
        [HttpPost]
        public virtual ResultModel Aggregate(
            [FromQuery] AggregateItem aggregateItem,
            SearchCondition<TDomainEntity> searchCondition)
        {
            searchCondition = searchCondition ?? new SearchCondition<TDomainEntity>();
            object result = GridService.Aggregate(
                aggregateItem,
                searchCondition.FilterSet,
                searchCondition.FilterClause,
                searchCondition.IdFilter?.IncludeOnly,
                searchCondition.IdFilter?.ExcludeAny,
                searchCondition.IsQuickSearch);
            return new ResultModel(System.Net.HttpStatusCode.OK, null, result ?? string.Empty);
        }

        /// <summary>
		/// get filter clause comment method
		/// </summary>
		/// <param name="filterSet">filter Set property</param>
		/// <returns></returns>
		[Route("rawfilterclause")]
        [HttpPost]
        public virtual ResultModel<string> GetRawFilterClause([FromBody] FilterSet filterSet)
        {
            string sql = GridService.GetClauseFromFilter(filterSet);

            // Test if sql clause works
            Search(new SearchCondition<TDomainEntity> { FilterClause = sql }, 0, 1, false);
            return new ResultModel<string>(sql);
        }

        /// <summary>
		/// get filter clause comment method
		/// </summary>
		/// <param name="filterSet">filter Set property</param>
		/// <returns></returns>
		[Route("filterclause")]
        [HttpPost]
        public virtual ResultModel<string> GetFilterClause([FromBody] FilterSet filterSet)
        {
            string sql = GridService.GetClauseFromFilter(filterSet);

            // Test if sql clause works
            Search(new SearchCondition<TDomainEntity> { FilterClause = sql }, null, null);
            return new ResultModel<string>(sql);
        }


        /// <summary>
        /// Get an extended entity of the current data type with different search conditions
        /// </summary>
        /// <param name="searchCondition">An object containing different search conditions</param>
        /// <param name="skip">Skip the top N records</param>
        /// <param name="take">Take N records after skipped amount</param>
        /// <param name="getCount">Should Total Count Be Set</param>
        [Route("")]
        [HttpPost]
        public virtual object Search(
            [FromBody] SearchCondition<TDomainEntity> searchCondition,
            int? skip = null,
            int? take = null,
            bool? getCount = true)
        {
            if (searchCondition != null && searchCondition.SortItems == null)
            {
                searchCondition.SortItems = new List<SortItem>();
            }

            searchCondition?.SortItems.Add(new SortItem { Direction = SortDirection.Ascending, Name = nameof(IGridEntity.Id) });
            AdjustSearchCondition(searchCondition);
            return SearchResult(searchCondition, skip, take, getCount, null);
        }

        protected virtual void AdjustSearchCondition(SearchCondition<TDomainEntity> searchCondition)
        {
            return; // adjust filter items
        }

        protected object SearchResult(
            SearchCondition<TDomainEntity> searchCondition,
            int? skip = null,
            int? take = null,
            bool? getCount = true,
            Func<IQueryable<TDomainEntity>, IQueryable<TDomainEntity>> filterFunc = null)
        {
            searchCondition = searchCondition ?? new SearchCondition<TDomainEntity>();
            if (searchCondition.Fields != null && !searchCondition.Fields.Contains(IdentityName))
            {
                searchCondition.Fields.Add(IdentityName);
            }

            try
            {
                SearchResult<TDomainEntity> searchResult = GetSearchResult(searchCondition, skip, take, getCount, filterFunc);
                return SearchResult(searchCondition, searchResult);
            }
            catch (Exception e)
            {
                // if the filter is not correct, response badrequest.
                var statusCode = (HttpStatusCode)e.HResult;
                return new ResultModel(statusCode == HttpStatusCode.PreconditionFailed ? statusCode : HttpStatusCode.BadRequest, e.Message);
            }
        }

        private SearchResult<TDomainEntity> GetSearchResult(
        SearchCondition<TDomainEntity> searchCondition,
        int? skip = null,
        int? take = null,
        bool? getCount = true,
        Func<IQueryable<TDomainEntity>, IQueryable<TDomainEntity>> filterFunc = null)
        {
            return GridService.Search(
                searchCondition.Fields,
                searchCondition.SortItems,
                searchCondition.FilterSet,
                searchCondition.FilterClause,
                searchCondition.IdFilter == null ? null : searchCondition.IdFilter.IncludeOnly,
                searchCondition.IdFilter == null ? null : searchCondition.IdFilter.ExcludeAny,
                skip,
                take,
                getCount,
                searchCondition.IsQuickSearch,
                filterFunc,
                searchCondition.IsDistinct);
        }

        private object SearchResult(SearchCondition<TDomainEntity> searchCondition, SearchResult<TDomainEntity> searchResult)
        {
            List<TDomainEntity> entities = searchResult.Entities.ToList();
            if (searchCondition.Fields != null && entities.Count > 0)
            {
                IEnumerable<dynamic> enityWithUDFs = GridService.ExtendDataEntities(entities.AsQueryable(), searchCondition.Fields, null, searchCondition.FilterSet, out _);

                if (searchCondition.IsDistinct && searchCondition.Fields.Count(r => r != IdentityName) == 1)
                {
                    enityWithUDFs = enityWithUDFs.GroupBy(r => ((IDictionary<string, object>)r)[searchCondition.Fields.FirstOrDefault(s => s != IdentityName)]).Select(r => r.Key).Distinct().AsEnumerable();
                }

                ResultModel<dynamic> response = new ResultModel<dynamic>(enityWithUDFs)
                {
                    TotalRecordCount = searchResult.TotalRecordCount,
                };

                response.SetFilteredRecordCount(searchResult.TotalCount);
                return response;
            }
            else
            {
                ResultModel<TDomainEntity> response = new ResultModel<TDomainEntity>(entities)
                {
                    TotalRecordCount = searchResult.TotalRecordCount,
                };

                response.SetFilteredRecordCount(searchResult.TotalCount);
                return response;
            }
        }
    }

    /// <summary>
    /// Base SearchController that will provide comment search method
    /// </summary>
    /// <typeparam name="TDomainEntity">entity</typeparam>
    public abstract class BaseSearchV2Controller<TDomainEntity> : BaseSearchV2Controller<TDomainEntity, ApiDBContent, int>
        where TDomainEntity : class, IGridEntity<int>, new()
    {
        public BaseSearchV2Controller(IBaseGridService<TDomainEntity> baseGridService)
            : base(baseGridService)
        {
        }
    }
}